﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using OpenQA.Selenium;

namespace Eswat.Services.WebDriver
{
	/// <summary>
	/// WebDriver test service.
	/// </summary>
	public class WebTestService : Finder, ITestService
	{
		/// <summary>
		/// The default name of this test service.
		/// </summary>
		public const string DefaultServiceName = "Web";

		private IWebDriver _driver;

		/// <summary>
		/// Initializes a new instance of the <see cref="WebTestService"/> class.
		/// </summary>
		public WebTestService()
			: this(DefaultServiceName)
		{
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="WebTestService"/> class.
		/// </summary>
		/// <param name="name">The name.</param>
		public WebTestService(string name)
		{
			this.Name = name;
		}

		/// <summary>
		/// Gets the unique service name.
		/// </summary>
		public string Name { get; private set; }

		/// <summary>
		/// Opens the specified web browser type.
		/// </summary>
		/// <param name="webBrowserType">Type of the web browser.</param>
		public void open(string webBrowserType)
		{
			const string ie = "IE";
			const string internetExplorer = "INTERNET EXPLORER";
			const string microsoftInternetExplorer = "MICROSOFT INTERNET EXPLORER";
			const string ff = "FF";
			const string fireFox = "FIREFOX";
			const string mozillaFireFox = "MOZILLA FIREFOX";
			const string chrome = "CHROME";
			const string googleChrome = "GOOGLE CHROME";

			var browserType = webBrowserType.ToUpperInvariant();

			WebBrowserType webBrowser = null;

			if (string.Compare(ie, browserType) == 0 || string.Compare(internetExplorer, browserType) == 0 || string.Compare(microsoftInternetExplorer, browserType) == 0) webBrowser = WebBrowserType.IE;
			else if (string.Compare(ff, browserType) == 0 || string.Compare(fireFox, browserType) == 0 || string.Compare(mozillaFireFox, browserType) == 0) webBrowser = WebBrowserType.Firefox;
			else if (string.Compare(chrome, browserType) == 0 || string.Compare(googleChrome, browserType) == 0) webBrowser = WebBrowserType.Chrome;

			if (webBrowser == null) throw new ArgumentException(string.Format("Unknown web browser type {0}: supported browsers include IE, FireFox, and Chrome", webBrowserType));

			this._driver = webBrowser.Create();
		}

		/// <summary>
		/// Goes the specified URL.
		/// </summary>
		/// <param name="url">The URL.</param>
		public void go(string url)
		{
			this._driver.Navigate().GoToUrl(url);
		}

		/// <summary>
		/// Backs this instance.
		/// </summary>
		public void back()
		{
			this._driver.Navigate().Back();
		}

		/// <summary>
		/// Forwards this instance.
		/// </summary>
		public void forward()
		{
			this._driver.Navigate().Forward();
		}

		/// <summary>
		/// Goes the specified page count.
		/// </summary>
		/// <param name="pageCount">The page count.</param>
		public void go(int pageCount)
		{
			if (pageCount == 0) this._driver.Navigate().Refresh();
			else if (pageCount < 0)
			{
				for (var i = pageCount; i == 0; i++)
					this._driver.Navigate().Back();
			}
			else
			{
				for (var i = 0; i < pageCount; i++)
					this._driver.Navigate().Forward();
			}
		}

		/// <summary>
		/// Executes the specified java script.
		/// </summary>
		/// <param name="javaScript">The java script.</param>
		/// <returns></returns>
		public dynamic js(string javaScript)
		{
			return (this._driver as IJavaScriptExecutor).ExecuteScript(javaScript);
		}

		/// <summary>
		/// Executes the specified java script.
		/// </summary>
		/// <param name="javaScript">The java script.</param>
		/// <param name="args">The args.</param>
		/// <returns></returns>
		internal dynamic js(string javaScript, params object[] args)
		{
			return (this._driver as IJavaScriptExecutor).ExecuteScript(javaScript, args);
		}

		/// <summary>
		/// Gets the divs.
		/// </summary>
		public ReadOnlyCollection<MyWebElement> divs
		{
			get { return this.findAll(By.TagName("div")); }
		}

		/// <summary>
		/// Gets the links.
		/// </summary>
		public ReadOnlyCollection<MyWebElement> links
		{
			get { return this.findAll(By.TagName("a")); }
		}

		/// <summary>
		/// Gets the images.
		/// </summary>
		public ReadOnlyCollection<MyWebElement> images
		{
			get { return this.findAll(By.TagName("img")); }
		}

		/// <summary>
		/// Gets the paragraphs.
		/// </summary>
		public ReadOnlyCollection<MyWebElement> paragraphs
		{
			get { return this.findAll(By.TagName("p")); }
		}

		/// <summary>
		/// Gets the articles.
		/// </summary>
		public ReadOnlyCollection<MyWebElement> articles
		{
			get { return this.findAll(By.TagName("article")); }
		}

		/// <summary>
		/// Gets the head.
		/// </summary>
		public MyWebElement head
		{
			get { return this.find(By.TagName("head")); }
		}

		/// <summary>
		/// Gets the body.
		/// </summary>
		public MyWebElement body
		{
			get { return this.find(By.TagName("body")); }
		}

		/// <summary>
		/// Gets the title.
		/// </summary>
		public MyWebElement title
		{
			get { return this.find(By.TagName("title")); }
		}

		/// <summary>
		/// Gets the scripts.
		/// </summary>
		public ReadOnlyCollection<MyWebElement> scripts
		{
			get { return this.findAll(By.TagName("script")); }
		}

		/// <summary>
		/// Close the current window, quitting the browser if it is the last window currently open.
		/// </summary>
		public void close()
		{
			this._driver.Close();

			if (this._driver.WindowHandles.Count == 0)
			{
				this._driver.Dispose();
				this._driver = null;
			}
		}

		/// <summary>
		/// Quits this driver, closing every associated window.
		/// </summary>
		public void quit()
		{
			this._driver.Quit();
			this._driver.Dispose();
			this._driver = null;
		}

		/// <summary>
		/// Gets the title of the current browser window.
		/// </summary>
		public string pageTitle
		{
			get { return this._driver.Title; }
		}

		/// <summary>
		/// Gets or sets the URL the browser is currently displaying.
		/// </summary>
		/// <value>
		/// The URL.
		/// </value>
		public string url
		{
			get
			{
				return this._driver.Url;
			}

			set
			{
				this._driver.Url = value;
			}
		}

		/// <summary>
		/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
		/// </summary>
		public void Dispose()
		{
			this._driver.Dispose();
		}

		/// <summary>
		/// Gets the search context.
		/// </summary>
		protected override ISearchContext SearchContext
		{
			get { return this._driver; }
		}

		/// <summary>
		/// Gets the search context.
		/// </summary>
		protected override WebTestService Web
		{
			get { return this; }
		}
	}

	/// <summary>
	/// 
	/// </summary>
	public abstract class Finder
	{
		/// <summary>
		/// Gets the search context.
		/// </summary>
		protected abstract ISearchContext SearchContext { get; }

		/// <summary>
		/// Gets the search context.
		/// </summary>
		protected abstract WebTestService Web { get; }

		/// <summary>
		/// Finds the specified by.
		/// </summary>
		/// <param name="by">The by.</param>
		/// <returns></returns>
		public MyWebElement find(By by)
		{
			return new MyWebElement(this.Web, this.SearchContext.FindElement(by));
		}

		/// <summary>
		/// Finds all.
		/// </summary>
		/// <param name="by">The by.</param>
		/// <returns></returns>
		public ReadOnlyCollection<MyWebElement> findAll(By by)
		{
			var elements = this.SearchContext.FindElements(by);
			var mys = new List<MyWebElement>(elements.Count);

			foreach (var element in elements)
			{
				mys.Add(new MyWebElement(this.Web, element));
			}

			return new ReadOnlyCollection<MyWebElement>(mys);
		}

		/// <summary>
		/// Bies the id.
		/// </summary>
		/// <param name="id">The id.</param>
		/// <returns></returns>
		public MyWebElement byId(string id)
		{
			return this.find(By.Id(id));
		}

		/// <summary>
		/// Ones the by tag.
		/// </summary>
		/// <param name="tagName">Name of the tag.</param>
		/// <returns></returns>
		public MyWebElement oneByTag(string tagName)
		{
			return this.find(By.TagName(tagName));
		}

		/// <summary>
		/// Ones the by id.
		/// </summary>
		/// <param name="id">The id.</param>
		/// <returns></returns>
		public MyWebElement oneById(string id)
		{
			return this.find(By.Id(id));
		}

		/// <summary>
		/// Ones the by CSS class.
		/// </summary>
		/// <param name="className">Name of the class.</param>
		/// <returns></returns>
		public MyWebElement oneByClass(string className)
		{
			return this.find(By.ClassName(className));
		}

		/// <summary>
		/// Ones the by CSS.
		/// </summary>
		/// <param name="selector">The selector.</param>
		/// <returns></returns>
		public MyWebElement oneByCSS(string selector)
		{
			return this.find(By.CssSelector(selector));
		}

		/// <summary>
		/// Ones the by link text.
		/// </summary>
		/// <param name="text">The text.</param>
		/// <returns></returns>
		public MyWebElement oneByLinkText(string text)
		{
			return this.find(By.LinkText(text));
		}

		/// <summary>
		/// Ones the name of the by.
		/// </summary>
		/// <param name="name">The name.</param>
		/// <returns></returns>
		public MyWebElement oneByName(string name)
		{
			return this.find(By.Name(name));
		}

		/// <summary>
		/// Ones the by partial link text.
		/// </summary>
		/// <param name="linkText">The link text.</param>
		/// <returns></returns>
		public MyWebElement oneByPartialLinkText(string linkText)
		{
			return this.find(By.PartialLinkText(linkText));
		}

		/// <summary>
		/// Ones the by X path.
		/// </summary>
		/// <param name="xpath">The xpath.</param>
		/// <returns></returns>
		public MyWebElement oneByXPath(string xpath)
		{
			return this.find(By.XPath(xpath));
		}

		/// <summary>
		/// Alls the by tag.
		/// </summary>
		/// <param name="tagName">Name of the tag.</param>
		/// <returns></returns>
		public ReadOnlyCollection<MyWebElement> allByTag(string tagName)
		{
			return this.findAll(By.TagName(tagName));
		}

		/// <summary>
		/// Alls the by CSS class.
		/// </summary>
		/// <param name="className">Name of the class.</param>
		/// <returns></returns>
		public ReadOnlyCollection<MyWebElement> allByClass(string className)
		{
			return this.findAll(By.ClassName(className));
		}

		/// <summary>
		/// Alls the by CSS.
		/// </summary>
		/// <param name="selector">The selector.</param>
		/// <returns></returns>
		public ReadOnlyCollection<MyWebElement> allByCSS(string selector)
		{
			return this.findAll(By.CssSelector(selector));
		}

		/// <summary>
		/// Alls the by link text.
		/// </summary>
		/// <param name="text">The text.</param>
		/// <returns></returns>
		public ReadOnlyCollection<MyWebElement> allByLinkText(string text)
		{
			return this.findAll(By.LinkText(text));
		}

		/// <summary>
		/// Alls the name of the by.
		/// </summary>
		/// <param name="name">The name.</param>
		/// <returns></returns>
		public ReadOnlyCollection<MyWebElement> allByName(string name)
		{
			return this.findAll(By.Name(name));
		}

		/// <summary>
		/// Alls the by partial link text.
		/// </summary>
		/// <param name="linkText">The link text.</param>
		/// <returns></returns>
		public ReadOnlyCollection<MyWebElement> allByPartialLinkText(string linkText)
		{
			return this.findAll(By.PartialLinkText(linkText));
		}

		/// <summary>
		/// Alls the by X path.
		/// </summary>
		/// <param name="xpath">The xpath.</param>
		/// <returns></returns>
		public ReadOnlyCollection<MyWebElement> allByXPath(string xpath)
		{
			return this.findAll(By.XPath(xpath));
		}
	}

	/// <summary>
	/// 
	/// </summary>
	public class MyWebElement : Finder
	{
		private readonly WebTestService _web;

		private readonly IWebElement _element;

		/// <summary>
		/// Initializes a new instance of the <see cref="MyWebElement"/> class.
		/// </summary>
		/// <param name="web">The web.</param>
		/// <param name="element">The element.</param>
		public MyWebElement(WebTestService web, IWebElement element)
		{
			this._web = web;
			this._element = element;
		}

		/// <summary>
		/// Clicks this instance.
		/// </summary>
		public void click()
		{
			this._element.Click();
		}

		/// <summary>
		/// Submits this instance.
		/// </summary>
		public void submit()
		{
			this._element.Submit();
		}

		/// <summary>
		/// Gets or sets the text.
		/// </summary>
		/// <value>
		/// The text.
		/// </value>
		public string text
		{
			get { return this._element.Text; }
			set { this._element.SendKeys(value); }
		}

		/// <summary>
		/// Gets the name of the tag.
		/// </summary>
		/// <value>
		/// The name of the tag.
		/// </value>
		public string tagName
		{
			get { return this._element.TagName; }
		}

		/// <summary>
		/// Types the specified value.
		/// </summary>
		/// <param name="value">The value.</param>
		public void type(string value)
		{
			this._element.Clear();
			this._element.SendKeys(value);
		}

		/// <summary>
		/// Appends the specified value.
		/// </summary>
		/// <param name="value">The value.</param>
		public void append(string value)
		{
			this._element.SendKeys(value);
		}

		/// <summary>
		/// Attrs the specified name.
		/// </summary>
		/// <param name="name">The name.</param>
		/// <returns></returns>
		public string attr(string name)
		{
			return this._element.GetAttribute(name);
		}

		/// <summary>
		/// Gets the HTML.
		/// </summary>
		public string html
		{
			get
			{
				return this._web.js("return this.outerHTML;", this._element);
			}
		}

		/// <summary>
		/// Gets the content.
		/// </summary>
		public string content
		{
			get
			{
				return this._web.js("return this.innerHTML;", this._element);
			}
		}

		/// <summary>
		/// CSSs the specified name.
		/// </summary>
		/// <param name="name">The name.</param>
		/// <returns></returns>
		public string css(string name)
		{
			return this._element.GetCssValue(name);
		}

		/// <summary>
		/// Gets the x.
		/// </summary>
		public int x
		{
			get { return this._element.Location.X; }
		}

		/// <summary>
		/// Gets the y.
		/// </summary>
		public int y
		{
			get { return this._element.Location.Y; }
		}

		/// <summary>
		/// Gets the width.
		/// </summary>
		public int width
		{
			get { return this._element.Size.Width; }
		}

		/// <summary>
		/// Gets the height.
		/// </summary>
		public int height
		{
			get { return this._element.Size.Height; }
		}

		/// <summary>
		/// Gets the search context.
		/// </summary>
		protected override ISearchContext SearchContext
		{
			get { return this._element; }
		}

		/// <summary>
		/// Gets the search context.
		/// </summary>
		protected override WebTestService Web
		{
			get { return this._web; }
		}
	}
}
